/*
 * @Author: xiaosihan 
 * @Date: 2023-08-04 17:17:15 
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2024-01-20 05:33:39
 */

import { BufferGeometry, Camera, Color, DoubleSide, Group, HalfFloatType, Material, Matrix4, Mesh, PerspectiveCamera, Plane, PlaneGeometry, Scene, ShaderMaterial, Vector3, Vector4, WebGLRenderTarget, WebGLRenderer } from "three";


// 镜子模型
export default class Mirror extends Mesh {

    constructor(geometry: BufferGeometry = Mirror.geometry) {
        super(geometry);
    }

    // 正方形网格
    static geometry = (() => {
        const geometry = new PlaneGeometry(10, 10, 1, 1);
        return geometry;
    })();

    isReflector = true;

    type = 'Reflector';

    camera = new PerspectiveCamera();

    reflectorPlane = new Plane();
    normal = new Vector3();
    reflectorWorldPosition = new Vector3();
    cameraWorldPosition = new Vector3();
    rotationMatrix = new Matrix4();
    lookAtPosition = new Vector3(0, 0, - 1);
    clipPlane = new Vector4();

    view = new Vector3();
    target = new Vector3();
    q = new Vector4();

    textureMatrix = new Matrix4();
    virtualCamera = this.camera;

    renderTarget = new WebGLRenderTarget(2048, 2048, { samples: 0, type: HalfFloatType });

    material = new ShaderMaterial({
        uniforms: {
            'color': {
                value: new Color("#b6b6b6")
            },

            'tDiffuse': {
                value: this.renderTarget.texture
            },

            'textureMatrix': {
                value: this.textureMatrix
            },
            blurAmount: {
                value: 0.5
            } // TODO 模糊程度，可以根据需求调整

        },

        vertexShader: /* glsl */`
                    uniform mat4 textureMatrix;
                    varying vec4 vUv;
        
                    #include <common>
                    #include <logdepthbuf_pars_vertex>
        
                    void main() {
        
                        vUv = textureMatrix * vec4( position, 1.0 );
        
                        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        
                        #include <logdepthbuf_vertex>
        
                    }`,

        fragmentShader: /* glsl */`
            uniform vec3 color;
            uniform sampler2D tDiffuse;
            varying vec4 vUv;
            uniform float blurAmount;

            #include <logdepthbuf_pars_fragment>

            float blendOverlay( float base, float blend ) {

                return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );

            }

            vec3 blendOverlay( vec3 base, vec3 blend ) {

                return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );

            }

            void main() {

                #include <logdepthbuf_fragment>

                vec2 texelSize = 1.0 / vec2(1024,1024);
                vec4 fragColor = vec4(0.0);
            
                  for (float offsetX = -blurAmount; offsetX <= blurAmount; offsetX += texelSize.x) {
                    for (float offsetY = -blurAmount; offsetY <= blurAmount; offsetY += texelSize.y) {
                        //  fragColor += texture2DProj(tDiffuse, vUv + vec4(offsetX, offsetY, 0.0, 0.0) * texelSize);
                         fragColor += texture2DProj(tDiffuse, vUv + vec4(offsetX * texelSize.x, offsetY * texelSize.y, 0.0, 0.0) );
                    }
                  }
            
                // fragColor /= pow(2.0 * blurAmount + 1.0, 2.0);
                // gl_FragColor = color;

                fragColor = texture2DProj( tDiffuse, vUv );
                gl_FragColor = vec4( blendOverlay( fragColor.rgb, color ), 1.0 );

                #include <tonemapping_fragment>
                #include <colorspace_fragment>
            }`,
        depthTest: true,
        depthWrite: true,
        transparent: true,
        side: DoubleSide
    });


    onBeforeRender = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => {

        this.reflectorWorldPosition.setFromMatrixPosition(this.matrixWorld);
        this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);

        this.rotationMatrix.extractRotation(this.matrixWorld);

        this.normal.set(0, 0, 1);
        this.normal.applyMatrix4(this.rotationMatrix);

        this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition);

        // Avoid rendering when reflector is facing away

        if (this.view.dot(this.normal) > 0) {
            this.normal.set(0, 0, -1);
        };

        this.view.reflect(this.normal).negate();
        this.view.add(this.reflectorWorldPosition);

        this.rotationMatrix.extractRotation(camera.matrixWorld);

        this.lookAtPosition.set(0, 0, - 1);
        this.lookAtPosition.applyMatrix4(this.rotationMatrix);
        this.lookAtPosition.add(this.cameraWorldPosition);

        this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition);
        this.target.reflect(this.normal).negate();
        this.target.add(this.reflectorWorldPosition);

        this.virtualCamera.position.copy(this.view);
        this.virtualCamera.up.set(0, 1, 0);
        this.virtualCamera.up.applyMatrix4(this.rotationMatrix);
        this.virtualCamera.up.reflect(this.normal);
        this.virtualCamera.lookAt(this.target);

        this.virtualCamera.far = this.camera.far; // Used in WebGLBackground

        this.virtualCamera.updateMatrixWorld();
        this.virtualCamera.projectionMatrix.copy(camera.projectionMatrix);

        // Update the texture matrix
        this.textureMatrix.set(
            0.5, 0.0, 0.0, 0.5,
            0.0, 0.5, 0.0, 0.5,
            0.0, 0.0, 0.5, 0.5,
            0.0, 0.0, 0.0, 1.0
        );
        this.textureMatrix.multiply(this.virtualCamera.projectionMatrix);
        this.textureMatrix.multiply(this.virtualCamera.matrixWorldInverse);
        this.textureMatrix.multiply(this.matrixWorld);

        // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
        // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
        this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition);
        this.reflectorPlane.applyMatrix4(this.virtualCamera.matrixWorldInverse);

        this.clipPlane.set(this.reflectorPlane.normal.x, this.reflectorPlane.normal.y, this.reflectorPlane.normal.z, this.reflectorPlane.constant);

        const projectionMatrix = this.virtualCamera.projectionMatrix;

        this.q.x = (Math.sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
        this.q.y = (Math.sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
        this.q.z = - 1.0;
        this.q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];

        // Calculate the scaled plane vector
        this.clipPlane.multiplyScalar(2.0 / this.clipPlane.dot(this.q));

        // Replacing the third row of the projection matrix
        projectionMatrix.elements[2] = this.clipPlane.x;
        projectionMatrix.elements[6] = this.clipPlane.y;
        projectionMatrix.elements[10] = this.clipPlane.z + 1.0 - 0.003;
        projectionMatrix.elements[14] = this.clipPlane.w;

        // Render
        this.visible = false;

        const currentRenderTarget = renderer.getRenderTarget();

        const currentXrEnabled = renderer.xr.enabled;
        const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;

        renderer.xr.enabled = false; // Avoid camera modification
        renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows

        renderer.setRenderTarget(this.renderTarget);

        renderer.state.buffers.depth.setMask(true); // make sure the depth buffer is writable so it can be properly cleared, see #18897

        if (renderer.autoClear === false) renderer.clear();
        renderer.render(scene, this.virtualCamera);

        renderer.xr.enabled = currentXrEnabled;
        renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;

        renderer.setRenderTarget(currentRenderTarget);

        // Restore viewport
        //@ts-ignore
        const viewport = this.camera.viewport;
        if (viewport !== undefined) {
            renderer.state.viewport(viewport);
        }

        this.visible = true;

    }

    getRenderTarget() {

        return this.renderTarget;

    };

    dispose() {
        this.renderTarget.dispose();
        // this.material.dispose();
    };

}